001 /*
002 * Copyright 2005 Stephen McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.station.exec;
020
021 import java.io.IOException;
022 import java.net.URI;
023 import java.rmi.registry.Registry;
024 import java.rmi.registry.LocateRegistry;
025 import java.util.Properties;
026 import java.util.Iterator;
027 import java.util.Set;
028
029 import net.dpml.component.Component;
030 import net.dpml.component.Model;
031 import net.dpml.component.Composition;
032 import net.dpml.component.ActivationPolicy;
033
034 import net.dpml.station.Station;
035 import net.dpml.station.Callback;
036 import net.dpml.station.ApplicationException;
037
038 import net.dpml.transit.Artifact;
039 import net.dpml.util.PropertyResolver;
040
041 import net.dpml.lang.PID;
042 import net.dpml.lang.Part;
043
044 import net.dpml.util.Logger;
045
046 import net.dpml.cli.Option;
047 import net.dpml.cli.Group;
048 import net.dpml.cli.CommandLine;
049 import net.dpml.cli.commandline.Parser;
050 import net.dpml.cli.util.HelpFormatter;
051 import net.dpml.cli.DisplaySetting;
052 import net.dpml.cli.builder.ArgumentBuilder;
053 import net.dpml.cli.builder.GroupBuilder;
054 import net.dpml.cli.builder.DefaultOptionBuilder;
055 import net.dpml.cli.builder.CommandBuilder;
056 import net.dpml.cli.option.PropertyOption;
057 import net.dpml.cli.validation.URIValidator;
058 import net.dpml.cli.validation.NumberValidator;
059
060 /**
061 * Generic application handler that establishes a dedicated handler based
062 * on the type of resource exposed by a codebase uri.
063 *
064 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
065 * @version 1.0.0
066 */
067 public class ApplicationHandler
068 {
069 private static final PID PROCESS_ID = new PID();
070
071 private final Logger m_logger;
072 private final Callback m_callback;
073
074 /**
075 * The application handler class is a generic component handler that delegates
076 * application deployment processes to dedicated handlers based on the codebase
077 * type of the target application. Normally this plugin is activated as a
078 * consequence of invoking the <tt>metro</tt> commandline handler (which is
079 * the default haviour of the <tt>station</tt> when deployment local processes).
080 * The following command list help about the <tt>metro</tt> commandline handler:
081 * <pre>$ metro help
082 Usage:
083 metro [-uri <uri> | -help]
084 options
085 -uri <uri> Execute deployment of an application codebase.
086 -key -port
087 -key (-k) <key> Station callback application key.
088 -port (-p) <port> Override default RMI registry port selection.
089 -help Print command help.
090 * </pre>
091 * A typical example of a commandline and resulting log of an application launch
092 * using <tt>metro</tt> is show below:
093 * <pre>
094 $ metro -uri link:part:dpml/planet/http/dpml-http-demo
095 [2236 ] [INFO ] (demo): Starting
096 [2236 ] [INFO ] (org.mortbay.http.HttpServer): Version Jetty/5.1.x
097 [2236 ] [INFO ] (org.mortbay.util.Container): Started net.dpml.http.impl.HttpServerImpl@6355dc
098 [2236 ] [INFO ] (org.mortbay.util.Credential): Checking Resource aliases
099 [2236 ] [INFO ] (org.mortbay.util.Container): Started HttpContext[/,/]
100 [2236 ] [INFO ] (org.mortbay.http.SocketListener): Started SocketListener on 0.0.0.0:8080
101 * </pre>
102 *
103 * When invoked in conjuction with the <tt>station</tt> two optional parameters may be
104 * supplied by the station. These include <tt>-port</tt> option which directs the implementation
105 * to locate the system wide <tt>station</tt> from an RMI registry on the selected port.
106 * The second option is the <tt>-key</tt> argument that the handler uses to to resolve a
107 * station callback through which the implementation notifies the station of the process
108 * identity and startup status. The callback process effectivly hands control over
109 * management of the JVM process lietime and root component to to the station.
110 *
111 * @param logger the assigned logging channel
112 * @param args command line arguments
113 * @exception Exception if an error occurs
114 */
115 public ApplicationHandler(
116 Logger logger, String[] args ) throws Exception
117 {
118 super();
119 m_logger = logger;
120
121 Thread.currentThread().setContextClassLoader( Composition.class.getClassLoader() );
122
123 if( logger.isDebugEnabled() )
124 {
125 for( int i=0; i<args.length; i++ )
126 {
127 logger.debug( "arg: " + ( i + 1 ) + ": " + args[i] );
128 }
129 }
130
131 Parser parser = new Parser();
132 parser.setGroup( COMMAND_GROUP );
133 CommandLine line = parser.parse( args );
134
135 if( line.hasOption( HELP_COMMAND ) || !line.hasOption( EXECUTE_COMMAND ) )
136 {
137 processHelp();
138 System.exit( 0 );
139 }
140
141 URI uri = (URI) line.getValue( EXECUTE_COMMAND, null );
142 if( null == uri )
143 {
144 throw new NullPointerException( "uri" ); // will not happen
145 }
146
147 String key = (String) line.getValue( KEY_OPTION, null );
148 if( null != key )
149 {
150 int port = Registry.REGISTRY_PORT;
151 if( line.hasOption( PORT_OPTION ) )
152 {
153 Number number =
154 (Number) line.getValue( PORT_OPTION, null );
155 port = number.intValue();
156 }
157
158 Station station = getStation( port );
159 m_callback = station.getCallback( key );
160 }
161 else
162 {
163 m_callback = new LocalCallback();
164 }
165
166
167 URI categories = getCategoriesURI( line );
168 Properties properties = getCommandLineProperties( line );
169 String raw = System.getProperty( "dpml.station.partition", "" );
170 String partition = PropertyResolver.resolve( raw );
171 Component component =
172 resolveTargetComponent(
173 logger, partition, uri, categories, properties );
174 m_callback.started( PROCESS_ID, component );
175 }
176
177 /**
178 * Return a properties instance composed of the <tt>-C<key>=<value></tt>
179 * commandline arguments.
180 * @param line the commandline
181 * @return the resolved properties
182 */
183 private Properties getCommandLineProperties( CommandLine line )
184 {
185 Properties properties = new Properties();
186 Set propertyValue = line.getProperties();
187 Iterator iterator = propertyValue.iterator();
188 while( iterator.hasNext() )
189 {
190 String name = (String) iterator.next();
191 String value = line.getProperty( name );
192 properties.setProperty( name, value );
193 }
194 return properties;
195 }
196
197 //------------------------------------------------------------------------------
198 // internals
199 //------------------------------------------------------------------------------
200
201 private URI getCategoriesURI( CommandLine line )
202 {
203 return (URI) line.getValue( LOGGING_OPTION, null );
204 }
205
206 private Component resolveTargetComponent(
207 Logger logger, String partition, URI uri, URI categories, Properties properties ) throws Exception
208 {
209 if( Artifact.isRecognized( uri ) )
210 {
211 Artifact artifact = Artifact.createArtifact( uri );
212 String type = artifact.getType();
213 if( type.equals( "part" ) )
214 {
215 Part part = Part.load( uri );
216 if( part instanceof Composition )
217 {
218 Composition composition = (Composition) part;
219 Model model = composition.getModel();
220 model.setActivationPolicy( ActivationPolicy.STARTUP );
221 return composition.newComponent();
222 }
223 }
224 }
225
226 final String error =
227 "URI not supported [" + uri + "].";
228 throw new ApplicationException( error );
229 }
230
231 private Station getStation( int port ) throws Exception
232 {
233 Registry registry = LocateRegistry.getRegistry( port );
234 return (Station) registry.lookup( Station.STATION_KEY );
235 }
236
237 private Logger getLogger()
238 {
239 return m_logger;
240 }
241
242 /**
243 * List general command help to the console.
244 * @exception IOException if an I/O error occurs
245 */
246 private void processHelp() throws IOException
247 {
248 HelpFormatter formatter = new HelpFormatter(
249 HelpFormatter.DEFAULT_GUTTER_LEFT,
250 HelpFormatter.DEFAULT_GUTTER_CENTER,
251 HelpFormatter.DEFAULT_GUTTER_RIGHT,
252 100 );
253
254 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
255 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
256 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
257
258 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
259 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
260 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
261 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
262 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
263 formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
264
265 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
266 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
267 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
268 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED );
269
270 formatter.setGroup( COMMAND_GROUP );
271 formatter.setShellCommand( "metro" );
272 formatter.print();
273 }
274
275 //-----------------------------------------------------------------------------
276 // CLI
277 //-----------------------------------------------------------------------------
278
279 private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder();
280 private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder();
281 private static final GroupBuilder GROUP_BUILDER = new GroupBuilder();
282 private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder();
283
284 private static final PropertyOption CONTEXT_OPTION =
285 new PropertyOption( "-C", "Set a context entry value.", 'C' );
286 private static final NumberValidator PORT_VALIDATOR = NumberValidator.getIntegerInstance();
287 private static final URIValidator URI_VALIDATOR = new URIValidator();
288
289 private static final Option LOGGING_OPTION =
290 OPTION_BUILDER
291 .withShortName( "categories" )
292 .withDescription( "Set logging category priorities." )
293 .withRequired( false )
294 .withArgument(
295 ARGUMENT_BUILDER
296 .withDescription( "URI." )
297 .withName( "uri" )
298 .withMinimum( 1 )
299 .withMaximum( 1 )
300 .withValidator( URI_VALIDATOR )
301 .create() )
302 .create();
303
304 private static final Option PORT_OPTION =
305 OPTION_BUILDER
306 .withShortName( "port" )
307 .withDescription( "Override default RMI registry port selection." )
308 .withRequired( false )
309 .withArgument(
310 ARGUMENT_BUILDER
311 .withDescription( "Port." )
312 .withName( "port" )
313 .withMinimum( 1 )
314 .withMaximum( 1 )
315 .withValidator( PORT_VALIDATOR )
316 .create() )
317 .create();
318
319 private static final Option KEY_OPTION =
320 OPTION_BUILDER
321 .withShortName( "key" )
322 .withShortName( "k" )
323 .withDescription( "Station callback application key." )
324 .withRequired( false )
325 .withArgument(
326 ARGUMENT_BUILDER
327 .withDescription( "Key." )
328 .withName( "key" )
329 .withMinimum( 1 )
330 .withMaximum( 1 )
331 .create() )
332 .create();
333
334
335 private static final Option HELP_COMMAND =
336 OPTION_BUILDER
337 .withShortName( "help" )
338 .withShortName( "h" )
339 .withDescription( "Print command help." )
340 .create();
341
342 private static final Group EXECUTE_GROUP =
343 GROUP_BUILDER
344 .withOption( KEY_OPTION )
345 .withOption( PORT_OPTION )
346 .withOption( CONTEXT_OPTION )
347 .withOption( LOGGING_OPTION )
348 .create();
349
350 private static final Option EXECUTE_COMMAND =
351 OPTION_BUILDER
352 .withShortName( "uri" )
353 .withDescription( "Execute deployment of an application codebase." )
354 .withChildren( EXECUTE_GROUP )
355 .withArgument(
356 ARGUMENT_BUILDER
357 .withDescription( "Application codebase uri." )
358 .withName( "uri" )
359 .withMinimum( 1 )
360 .withMaximum( 1 )
361 .withValidator( new URIValidator() )
362 .create() )
363 .create();
364
365 private static final Group COMMAND_GROUP =
366 GROUP_BUILDER
367 .withName( "options" )
368 .withMinimum( 1 )
369 .withMaximum( 1 )
370 .withOption( EXECUTE_COMMAND )
371 .withOption( HELP_COMMAND )
372 .create();
373
374 /**
375 * Internal class supporting callback semantics used in cases where the application
376 * handler has to assume management of a target.
377 */
378 private class LocalCallback implements Callback
379 {
380 private PID m_pid;
381 private Component m_handler;
382
383 /**
384 * Method invoked by a process to signal that the process has started.
385 *
386 * @param pid the process identifier
387 * @param handler optional handler reference
388 * @exception RemoteException if a remote error occurs
389 */
390 public void started( PID pid, Component handler )
391 {
392 m_pid = pid;
393 m_handler = handler;
394 try
395 {
396 handler.commission();
397 }
398 catch( Exception e )
399 {
400 final String error =
401 "Initiating process deactivation due to an application error.";
402 getLogger().error( error, e );
403 try
404 {
405 handler.decommission();
406 }
407 catch( Exception deactivationError )
408 {
409 // ignore
410 }
411 }
412 }
413
414 /**
415 * Method invoked by a process to signal that the process has
416 * encounter an error condition.
417 *
418 * @param throwable the error condition
419 * @param fatal if true the process is requesting termination
420 * @exception RemoteException if a remote error occurs
421 */
422 public void error( Throwable throwable, boolean fatal )
423 {
424 final String error = "Application error.";
425 if( fatal )
426 {
427 getLogger().error( error, throwable );
428 try
429 {
430 m_handler.decommission();
431 }
432 catch( Throwable e )
433 {
434 }
435 }
436 else
437 {
438 getLogger().warn( error, throwable );
439 }
440 }
441
442 /**
443 * Method invoked by a process to send a arbitary message to the
444 * the callback handler.
445 *
446 * @param message the message
447 * @exception RemoteException if a remote error occurs
448 */
449 public void info( String message )
450 {
451 getLogger().info( message );
452 }
453
454 /**
455 * Method invoked by a process to signal its imminent termination.
456 *
457 * @exception RemoteException if a remote error occurs
458 */
459 public void stopped()
460 {
461 Thread thread = new Thread(
462 new Runnable()
463 {
464 public void run()
465 {
466 System.exit( 0 );
467 }
468 }
469 );
470 thread.start();
471 }
472 }
473 }